#include "Compiler.h"
#include <UnitTest++.h>
#include <iostream>
#include "vcc.h"
#include "memory.h"
#include "ExpectedTokenException.h"

SUITE(CompilerTest) {

    TEST(starts_with_parsing_offset_of_zero) {
        Compiler compiler("");
        CHECK_EQUAL(0U, compiler.get_parsing_offset());
    }

    TEST(can_skip_whitespace_and_get_parsing_offset) {
        std::string s = " //\n/**/\t\r\n";
        Compiler compiler((char*)s.c_str());
        compiler.skip_whitespace();
        CHECK_EQUAL(s.length(), compiler.get_parsing_offset());
    }

    TEST(can_expect_but_not_find) {
        Compiler compiler("foo");
        CHECK_THROW(compiler.expect("bar"), ExpectedTokenException);
    }

    TEST(can_expect_and_find) {
        Compiler compiler("foo");
        compiler.expect("foo");
    }

    TEST(is_at_cpp_comment) {
        Compiler compiler("//");
        CHECK(compiler.at_cpp_comment());
        CHECK_EQUAL(0U, compiler.get_parsing_offset());
    }

    TEST(can_skip_cpp_comment) {
        Compiler compiler("//");
        compiler.skip_cpp_comment();
        CHECK_EQUAL(2U, compiler.get_parsing_offset());
    }

    TEST(skipping_c_comment_throws_exception_if_unclosed) {
        Compiler compiler("/*");
        CHECK_THROW(compiler.skip_c_comment(), ExpectedTokenException);
    }

    TEST(is_at_c_comment) {
        Compiler compiler("/*");
        CHECK(compiler.at_c_comment());
    }

    TEST(is_at_c_comment_end) {
        Compiler compiler("*/");
        CHECK(compiler.at_c_comment_end());
    }

    TEST(can_skip_c_comment) {
        Compiler compiler("/**/");
        compiler.skip_c_comment();
        CHECK_EQUAL(4U, compiler.get_parsing_offset());
    }

    TEST(is_at_whitespace) {
        Compiler compiler(" x");
        CHECK(compiler.at_whitespace());
    }

    TEST(is_not_at_whitespace) {
        Compiler compiler("x ");
        CHECK(!compiler.at_whitespace());
    }

    TEST(can_get_token) {
        Compiler compiler(" test");
        Token token = compiler.get_token();
        CHECK_EQUAL("test", token.get_text());
    }

    TEST(can_get_many_tokens) {
        Compiler compiler(" 123\tfoo\r\n bar");
        CHECK_EQUAL("123", compiler.get_token().get_text());
        CHECK_EQUAL("foo", compiler.get_token().get_text());
        CHECK_EQUAL("bar", compiler.get_token().get_text());
    }

    TEST(cannot_get_token) {
        Compiler compiler(" ");
        Token token = compiler.get_token();
        CHECK_EQUAL("", token.get_text());
    }

    TEST(can_parse_script) {
        char* data = LoadTextFile("../samples/vc/bumville.vc");
        Compiler compiler(data);
        while (!compiler.at_end()) {
            compiler.get_token();
        }
        CHECK_EQUAL(29170U, compiler.get_parsing_offset());
        FreeMemory(data);
    }

    TEST(can_parse_event) {
        Compiler compiler("event{}");
        compiler.parse_event();
        CHECK(compiler.at_end());
    }

    TEST(cannot_parse_event_with_misspelled_block_name) {
        Compiler compiler("evnt{}");
        CHECK_THROW(compiler.parse_event(), ExpectedTokenException);
    }

    TEST(cannot_parse_event_with_missing_open_brace) {
        Compiler compiler("event}");
        CHECK_THROW(compiler.parse_event(), ExpectedTokenException);
    }

    TEST(cannot_parse_event_with_missing_closing_brace) {
        Compiler compiler("event{");
        CHECK_THROW(compiler.parse_event(), ExpectedTokenException);
    }

    TEST(can_detect_library_function) {
        Compiler compiler("mapswitch");
        Token token = compiler.get_token();
        CHECK(compiler.is_library_function(token));
    }

    TEST(can_detect_is_not_library_function) {
        Compiler compiler("dumbstuff");
        Token token = compiler.get_token();
        CHECK(!compiler.is_library_function(token));
    }

    TEST(can_parse_term) {
        Compiler compiler("9");
        CHECK_EQUAL(9, compiler.parse_term());
    }

    TEST(can_parse_simple_addition_expression) {
        Compiler compiler("9 + 3");
        CHECK_EQUAL(12, compiler.parse_expression());
    }

    TEST(can_parse_simple_subtraction_expression) {
        Compiler compiler("3 - 9");
        CHECK_EQUAL(-6, compiler.parse_expression());
    }

    TEST(can_parse_simple_multiplication_expression) {
        Compiler compiler("5 * 6");
        CHECK_EQUAL(30, compiler.parse_expression());
    }

    TEST(can_parse_simple_division_expression) {
        Compiler compiler("10 / 2");
        CHECK_EQUAL(5, compiler.parse_expression());
    }

    TEST(can_parse_expression_with_multiple_operators) {
        Compiler compiler("1 + 5 - 3 + 4 * 2 / 4");
        CHECK_EQUAL(5, compiler.parse_expression());
    }

    TEST(can_not_parse_invalid_expression) {
        Compiler compiler("3 x 9");
        CHECK_THROW(compiler.parse_expression(), ExpectedTokenException);
    }

    TEST(can_look_ahead_one_token) {
        Compiler compiler("abc xyz");
        compiler.get_token();
        CHECK_EQUAL("xyz", compiler.look_ahead().get_text());
    }

    TEST(can_look_ahead_then_get_the_same_token) {
        Compiler compiler("abc 123");
        compiler.get_token();
        CHECK_EQUAL("123", compiler.look_ahead().get_text());
        CHECK_EQUAL("123", compiler.get_token().get_text());
    }

    TEST(can_look_ahead_without_affecting_parsing_offset) {
        Compiler compiler("abc xyz");
        compiler.get_token();
        CHECK_EQUAL(3U, compiler.get_parsing_offset());
        compiler.look_ahead();
        CHECK_EQUAL(3U, compiler.get_parsing_offset());
    }

    TEST(can_parse_literal_expression) {
        Compiler compiler("4");
        CHECK_EQUAL(4, compiler.parse_expression());
    }

    TEST(can_parse_expression_using_precedence) {
        Compiler compiler("4 + 3 * 2 - 10 / 4");
        CHECK_EQUAL(8, compiler.parse_expression());
    }

    TEST(can_parse_literal_parenthesized_expression) {
        Compiler compiler("(4)");
        CHECK_EQUAL(4, compiler.parse_expression());
    }

    TEST(can_parse_simple_parenthesized_expression) {
        Compiler compiler("(4 + 5)");
        CHECK_EQUAL(9, compiler.parse_expression());
    }

    TEST(can_parse_parenthesized_expression_and_stuff) {
        Compiler compiler("(7 + 6) - 4");
        CHECK_EQUAL(9, compiler.parse_expression());
    }

    TEST(can_parse_stuff_and_parenthesized_expression) {
        Compiler compiler("3 + (8 - 5)");
        CHECK_EQUAL(6, compiler.parse_expression());
    }

    TEST(can_parse_literal_parenthesized_expression_nested) {
        Compiler compiler("(((3)))");
        CHECK_EQUAL(3, compiler.parse_expression());
    }

    TEST(can_parse_complex_parenthesized_expression) {
        Compiler compiler("((19 - ((8 - 2) * 3 / (1 + 1))) - 5) * 20 - (((((5)))))");
        CHECK_EQUAL(95, compiler.parse_expression());
    }

    TEST(can_parse_factor) {
        Compiler compiler("123456789");
        CHECK_EQUAL(123456789, compiler.parse_factor());
    }

    TEST(cannot_parse_factor) {
        Compiler compiler("abc");
        CHECK_THROW(compiler.parse_factor(), VccException);
    }
}
